package org.infodavid.common.persistence.impl;

import java.io.Serializable;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.FlushModeType;
import javax.persistence.LockModeType;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceException;
import javax.persistence.Query;
import javax.persistence.criteria.CriteriaBuilder;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.NonUniqueObjectException;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.ejb.EntityManagerImpl;
import org.hibernate.internal.SessionImpl;
import org.hibernate.search.jpa.Search;
import org.hibernate.search.jpa.impl.FullTextEntityManagerImpl;
import org.infodavid.common.persistence.IBasicPersistenceProvider;
import org.infodavid.common.persistence.ILockable;
import org.infodavid.common.persistence.ILockable.ELockType;
import org.infodavid.common.persistence.IPersistentData;
import org.infodavid.common.persistence.IQueryParameter;
import org.infodavid.common.persistence.ITransaction;
import org.infodavid.common.persistence.PersistenceSession;
import org.infodavid.common.util.IVisitor;

/**
 * The Class JpaPersistenceSession.
 */
@SuppressWarnings({
    "unchecked", "rawtypes"
})
public class HibernatePersistenceSession extends PersistenceSession {

  /**
   * The Class TransactionAdapter.
   */
  protected class TransactionAdapter implements ITransaction {

    /** The logger. */
    private final Log LOGGER = LogFactory.getLog(TransactionAdapter.class);

    /** The delegate. */
    private transient Transaction delegate = null;

    /** The session. */
    private final transient HibernatePersistenceSession session;

    /**
     * The Constructor.
     * @param pSession the session
     */
    public TransactionAdapter(final HibernatePersistenceSession pSession) {
      if (pSession == null) {
        throw new IllegalArgumentException("Delegate transaction can't be null");
      }

      session = pSession;
    }

    /*
     * (non-Javadoc)
     * @see org.infodavid.common.persistence.ITransaction#begin()
     */
    @Override
    public void begin() throws PersistenceException {
      if (isDebugEnabled) {
        LOGGER.debug("Starting the transaction of session: " + session);
      }

      try {
        delegate = session.getDelegate().beginTransaction();
      }
      catch (final Exception e) {
        throw new PersistenceException("An error occurs while starting transaction", e);
      }
    }

    /*
     * (non-Javadoc)
     * @see org.infodavid.common.persistence.ITransaction#commit()
     */
    @Override
    public void commit() throws PersistenceException {
      if (isDebugEnabled) {
        LOGGER.debug("Committing the transaction of session: " + session);
      }

      try {
        getDelegate().commit();
      }
      catch (final Exception e) {
        throw new PersistenceException("An error occurs while committing transaction", e);
      }
    }

    /**
     * Gets the delegate.
     * @return the delegate
     */
    public Transaction getDelegate() {
      return delegate;
    }

    /*
     * (non-Javadoc)
     * @see org.infodavid.common.persistence.ITransaction#isActive()
     */
    @Override
    public boolean isActive() throws PersistenceException {
      return getDelegate() != null && getDelegate().isActive();
    }

    /*
     * (non-Javadoc)
     * @see org.infodavid.common.persistence.ITransaction#rollback()
     */
    @Override
    public void rollback() throws PersistenceException {
      if (isActive()) {
        if (isDebugEnabled) {
          LOGGER.debug("Rolling back the transaction of session: " + session);
        }

        try {
          getDelegate().rollback();
          session.getDelegate().clear();
        }
        catch (final Exception e) {
          throw new PersistenceException("An error occurs while rolling back transaction", e);
        }
      }
    }
  }

  /** logger. */
  private static final Log LOG = LogFactory.getLog(HibernatePersistenceSession.class);

  /** The debug flag. */
  private final boolean isDebugEnabled = LOG.isDebugEnabled();

  /** The transaction. */
  private transient ITransaction transaction = null;

  /** The basic manager. */
  private EntityManager manager = null;

  /** The shared map of hints. */
  private final Map<String,Object> hints = new HashMap<String,Object>();

  /**
   * The Constructor.
   * @param provider the provider
   * @param factory the factory
   * @param useFullText the use full text
   * @throws PersistenceException the exception
   */
  protected HibernatePersistenceSession(final IBasicPersistenceProvider provider,
      final EntityManagerFactory factory, final boolean useFullText) throws PersistenceException {
    super(provider);

    manager = factory.createEntityManager();
    manager.setFlushMode(FlushModeType.COMMIT);

    if (useFullText) {
      manager = Search.getFullTextEntityManager(manager);
      manager.setFlushMode(FlushModeType.COMMIT);
    }
  }

  /*
   * (non-Javadoc)
   * @see org.infodavid.common.persistence.PersistenceSession#doClose()
   */
  @Override
  protected void doClose() throws PersistenceException {
    if (getDelegate() != null) {
      final Session session = getDelegate();

      try {
        session.clear();
        session.disconnect();
      }
      catch (final Throwable e) {
        throw new PersistenceException("An error occurs while clearing session", e);
      }
    }

    if (manager != null) {
      try {
        manager.clear();
      }
      catch (final Throwable e) {
        throw new PersistenceException("An error occurs while clearing session", e);
      }

      try {
        manager.close();
        getProvider().release(this);
      }
      catch (final Throwable e) {
        throw new PersistenceException("An error occurs while closing session", e);
      }
    }
  }

  /**
   * See super class or interface. (non-Javadoc)
   * @throws PersistenceException the persistence exception
   * @see org.infodavid.common.persistence.PersistenceSession#flush()
   */
  @Override
  public void flush() throws PersistenceException {
    if (manager == null || !manager.isOpen()) {
      LOG.warn("Flushing skipped, session is invalid");
    }
    else {
      try {
        if (isTransactionActive()) {
          manager.flush();
        }
        else {
          LOG.debug("Flushing skipped, transaction is not active");
        }
      }
      catch (final Throwable e) {
        throw new PersistenceException("An error occurs while flushing session", e);
      }
    }
  }

  /*
   * (non-Javadoc)
   * @see org.infodavid.common.persistence.PersistenceSession#doFindEntities(java .lang.Class,
   * java.lang.String, java.util.List)
   */
  @Override
  protected <T extends IPersistentData> List<T> doFindEntities(final Class<T> arg0,
      final String queryName, final List<IQueryParameter> params) throws PersistenceException {
    try {
      final Query query = manager.createNamedQuery(queryName);

      HibernateHelper.getInstance().applyParameters(params, query);

      return query.getResultList();
    }
    catch (final Throwable e) {
      handle(arg0, null, e);

      return null;
    }
  }

  /**
   * To lock mode type.
   * @param type the type
   * @return the lock mode type
   */
  private LockModeType toLockModeType(final ILockable.ELockType type,
      final Map<String,Object> properties) {
    LockModeType mode;

    if (type == ILockable.ELockType.READ) {
      mode = LockModeType.READ;
    }
    else if (type == ILockable.ELockType.NO_WAIT) {
      mode = LockModeType.PESSIMISTIC_WRITE;
      properties.put("javax.persistence.lock.timeout", Integer.valueOf(0));
    }
    else {
      mode = LockModeType.PESSIMISTIC_WRITE;
    }

    return mode;
  }

  /**
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.persistence.PersistenceSession#doFindEntity(java.lang.Class,
   *      java.io.Serializable, org.infodavid.common.persistence.ILockable.ELockType)
   */
  @Override
  protected <T extends IPersistentData> T doFindEntity(final Class<T> arg0, final Serializable key,
      final ILockable.ELockType lockType) throws PersistenceException {
    T result = null;

    try {
      if (lockType == null) {
        result = manager.find(arg0, key);
      }
      else {
        synchronized (hints) {
          final LockModeType lockModeType = toLockModeType(lockType, hints);
          result = manager.find(arg0, key, lockModeType, hints);
          hints.clear();
        }
      }
    }
    catch (final Throwable e) {
      handle(arg0, key, e);
    }

    if (isDebugEnabled) {
      LOG.debug("Query result: " + result);
    }

    return result;
  }

  /**
   * See super class or interface. (non-Javadoc)
   * @param <T> the generic type
   * @param clazz the clazz
   * @param queryName the query name
   * @param params the params
   * @param single the single
   * @return the object
   * @throws PersistenceException the persistence exception
   * @see org.infodavid.common.persistence.PersistenceSession#doExecuteQuery(java.lang.Class,
   *      java.lang.String, java.util.List, boolean)
   */
  @Override
  protected <T> Object doExecuteNamedQuery(final Class<T> clazz, final String queryName,
      final List<IQueryParameter> params, final boolean single) throws PersistenceException {
    Object result = null;

    try {
      final Query query = manager.createNamedQuery(queryName);

      HibernateHelper.getInstance().applyParameters(params, query);

      if (isDebugEnabled) {
        LOG.debug("Query: " + query.toString());
      }

      if (single) {
        result = query.getSingleResult();
      }
      else {
        result = query.getResultList();
      }
    }
    catch (final NoResultException e) {}
    catch (final Throwable e) {
      handle(clazz, null, e);
    }

    if (isDebugEnabled) {
      LOG.debug("Query result: " + result);
    }

    return result;
  }

  /**
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.persistence.PersistenceSession#doExecuteQuery(java.lang.Class,
   *      java.lang.String, java.util.List, boolean)
   */
  @Override
  protected <T> Object doExecuteQuery(Class<T> clazz, String qlString,
      List<IQueryParameter> parameters, boolean single) throws PersistenceException {
    Object result = null;

    try {
      final Query query = manager.createQuery(qlString);

      HibernateHelper.getInstance().applyParameters(parameters, query);

      if (isDebugEnabled) {
        LOG.debug("Query: " + query.toString());
      }

      if (single) {
        result = query.getSingleResult();
      }
      else {
        result = query.getResultList();
      }
    }
    catch (final NoResultException e) {}
    catch (final Throwable e) {
      handle(clazz, null, e);
    }

    if (isDebugEnabled) {
      LOG.debug("Query result: " + result);
    }

    return result;
  }

  /*
   * (non-Javadoc)
   * @see org.infodavid.common.persistence.PersistenceSession#getDelegate()
   */
  @Override
  public org.hibernate.Session getDelegate() throws PersistenceException {
    try {
      if (manager instanceof EntityManagerImpl) {
        return ((EntityManagerImpl)manager).getSession();
      }
      else if (manager instanceof FullTextEntityManagerImpl) {
        return (SessionImpl)((FullTextEntityManagerImpl)manager).getDelegate();
      }
    }
    catch (final Throwable e) {
      throw new PersistenceException("An error occurs while retrieving delegate", e);
    }

    return null;
  }

  /*
   * (non-Javadoc)
   * @see org.infodavid.common.persistence.PersistenceSession#getConnection()
   */
  @Override
  public Connection getConnection() throws PersistenceException {
    try {
      return ((SessionImpl)manager.getDelegate()).getJdbcConnectionAccess().obtainConnection();
    }
    catch (final Exception e) {
      throw new PersistenceException("Can not retrieve JDBC connection", e);
    }
  }

  /*
   * (non-Javadoc)
   * @see org.infodavid.common.persistence.PersistenceSession#doGetTransaction()
   */
  @Override
  protected synchronized ITransaction doGetTransaction() throws PersistenceException {
    if (transaction == null) {
      try {
        buildTransactionAdapter();
      }
      catch (final Throwable e) {
        throw new PersistenceException("An error occurs while retrieving transaction", e);
      }
    }

    return transaction;
  }

  /**
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.persistence.PersistenceSession#doInsertEntities(java.util.Collection,
   *      org.infodavid.common.util.IVisitor)
   */
  @Override
  protected <T extends IPersistentData> List<T> doInsertEntities(final Collection<T> entities,
      final IVisitor<T,Object> visitor) throws PersistenceException {
    final List<T> inserted = new ArrayList<T>(entities.size());

    if (visitor == null) {
      for (final T entity : entities) {
        try {
          manager.persist(entity);
          inserted.add(entity);
        }
        catch (final IllegalStateException e) {
          if (LOG.isDebugEnabled()) {
            LOG.debug("Trying to update entity instead of inserting it", e);
          }

          inserted.add(manager.merge(entity));
        }
        catch (final Throwable e) {
          handle(entity.getClass(), entity.getKey(), e);
        }
      }
    }
    else {
      for (final T entity : entities) {
        try {
          visitor.visit(entity);
          manager.persist(entity);
          inserted.add(entity);
        }
        catch (final IllegalStateException e) {
          if (LOG.isDebugEnabled()) {
            LOG.debug("Trying to update entity instead of inserting it", e);
          }

          inserted.add(manager.merge(entity));
        }
        catch (final Throwable e) {
          handle(entity.getClass(), entity.getKey(), e);
        }
      }
    }

    return inserted;
  }

  /**
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.persistence.PersistenceSession#doInsertEntity(org.infodavid.common.persistence.IPersistentData)
   */
  @Override
  protected <T extends IPersistentData> T doInsertEntity(final T entity)
      throws PersistenceException {
    T result = null;

    try {
      manager.persist(entity);
      result = entity;
    }
    catch (final IllegalStateException e) {
      if (LOG.isDebugEnabled()) {
        LOG.debug("Trying to update entity instead of inserting it", e);
      }

      result = manager.merge(entity);
    }
    catch (final Throwable e) {
      handle(entity.getClass(), entity.getKey(), e);

      result = null;
    }

    if (isDebugEnabled) {
      LOG.debug("Query result: " + result);
    }

    return result;
  }

  /*
   * (non-Javadoc)
   * @see org.infodavid.common.persistence.PersistenceSession#isOpen()
   */
  @Override
  public boolean isOpen() throws PersistenceException {
    if (isDebugEnabled) {
      LOG.debug("Checking connection state");
    }

    try {
      return manager != null && manager.isOpen();
    }
    catch (final Throwable e) {
      throw new PersistenceException("An error occurs while retrieving state", e);
    }
  }

  /**
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.persistence.PersistenceSession#doLock(org.infodavid.common.persistence.ILockable,
   *      org.infodavid.common.persistence.ILockable.ELockType)
   */
  @Override
  protected void doLock(final ILockable lockable, final ILockable.ELockType type)
      throws PersistenceException {
    try {
      synchronized (hints) {
        final LockModeType lockModeType = toLockModeType(type, hints);
        manager.lock(lockable, lockModeType, hints);
        hints.clear();
      }
    }
    catch (final Throwable e) {
      handle(lockable.getClass(), lockable, e);
    }
  }

  /**
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.persistence.PersistenceSession#doRemoveEntities(java.util.Collection,
   *      org.infodavid.common.util.IVisitor)
   */
  @Override
  protected <T extends IPersistentData> void doRemoveEntities(final Collection<T> entities,
      final IVisitor<T,Object> visitor) throws PersistenceException {
    final Iterator<T> ite = entities.iterator();
    T entity = null;

    try {
      if (visitor == null) {
        while (ite.hasNext()) {
          entity = ite.next();

          if (entity.getKey() == null) {
            if (isDebugEnabled) {
              LOG.debug("Not a persisted entity passed to remove method: " + entity);
            }
          }
          else {
            manager.remove(entity);
          }
        }
      }
      else {
        while (ite.hasNext()) {
          entity = ite.next();

          if (entity.getKey() == null) {
            if (isDebugEnabled) {
              LOG.debug("Not a persisted entity passed to remove method: " + entity);
            }
          }
          else {
            visitor.visit(entity);
            manager.remove(entity);
          }
        }
      }

      flush();
    }
    catch (final Throwable e) {
      handle(entity == null ? null : entity.getClass(), entity == null ? null : entity.getKey(), e);
    }
  }

  /**
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.persistence.PersistenceSession#doRemove(java.lang.Class,
   *      java.util.Collection, org.infodavid.common.util.IVisitor)
   */
  @Override
  protected <K extends Serializable,T extends IPersistentData<K>> void doRemove(
      final Class<T> entityClass, final Collection<K> keys, final IVisitor<K,Object> visitor)
      throws PersistenceException {
    final Iterator<K> ite = keys.iterator();
    K key = null;
    T entity;

    try {

      if (visitor == null) {
        while (ite.hasNext()) {
          key = ite.next();

          if (key == null) {
            if (isDebugEnabled) {
              LOG.debug("Not a key passed to remove method: " + key);
            }
          }
          else {
            entity = manager.find(entityClass, key);

            if (entity == null) {
              throw new IllegalArgumentException("entity");
            }

            manager.remove(entity);
          }
        }
      }
      else {
        while (ite.hasNext()) {
          key = ite.next();

          if (key == null) {
            if (isDebugEnabled) {
              LOG.debug("Not a key passed to remove method: " + key);
            }
          }
          else {
            visitor.visit(key);

            entity = manager.find(entityClass, key);

            if (entity == null) {
              throw new IllegalArgumentException("entity");
            }

            manager.remove(entity);
          }
        }
      }

      flush();
    }
    catch (final IllegalArgumentException e) {
      throw e;
    }
    catch (final Throwable e) {
      handle(entityClass, key, e);
    }
  }

  /**
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.persistence.PersistenceSession#doRemoveEntity(org.infodavid.common.persistence.IPersistentData)
   */
  @Override
  protected <T extends IPersistentData> void doRemoveEntity(final T entity)
      throws PersistenceException {
    try {
      if (entity == null || entity.getKey() == null) {
        if (isDebugEnabled) {
          LOG.debug("Not a persisted entity passed to remove method: " + entity);
        }

        throw new IllegalArgumentException("entity");
      }

      manager.remove(entity);
    }
    catch (final IllegalArgumentException e) {
      throw e;
    }
    catch (final Throwable e) {
      handle(entity, e);
    }
  }

  /**
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.persistence.PersistenceSession#doRemove(java.lang.Class,
   *      java.io.Serializable)
   */
  @Override
  protected <K extends Serializable,T extends IPersistentData<K>> void doRemove(
      final Class<T> entityClass, final K key) throws PersistenceException {
    try {
      if (key == null) {
        if (isDebugEnabled) {
          LOG.debug("Null key passed to remove method");
        }

        throw new IllegalArgumentException("entity");
      }

      final T entity = manager.find(entityClass, key);

      if (entity == null) {
        throw new IllegalArgumentException("entity");
      }

      manager.remove(entity);
    }
    catch (final IllegalArgumentException e) {
      throw e;
    }
    catch (final Throwable e) {
      handle(entityClass, key, e);
    }
  }

  /**
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.persistence.PersistenceSession#doUpdateEntities(java.util.Collection,
   *      org.infodavid.common.util.IVisitor)
   */
  @Override
  protected <T extends IPersistentData> Collection<T> doUpdateEntities(
      final Collection<T> entities, final IVisitor<T,Object> visitor) throws PersistenceException {
    final List<T> inserted = new ArrayList<T>(entities.size());
    final SessionImpl session = (SessionImpl)manager.getDelegate();
    final Iterator<T> ite = entities.iterator();
    String entityName = null;
    T entity;

    if (visitor == null) {
      while (ite.hasNext()) {
        entity = ite.next();

        try {
          session.merge(entity);
        }
        catch (final NonUniqueObjectException e) {
          if (StringUtils.isEmpty(entityName)) {
            entityName = session.bestGuessEntityName(entity);
          }

          final T existing = (T)session.get(entityName, entity.getKey());

          session.evict(existing);
          session.merge(entity);
        }
        catch (final Throwable e) {
          handle(entity.getClass(), entity.getKey(), e);
        }

        inserted.add(entity);
      }
    }
    else {
      while (ite.hasNext()) {
        entity = ite.next();

        try {
          visitor.visit(entity);
          session.merge(entity);
        }
        catch (final NonUniqueObjectException e) {
          if (StringUtils.isEmpty(entityName)) {
            entityName = session.bestGuessEntityName(entity);
          }

          final T existing = (T)session.get(entityName, entity.getKey());

          session.evict(existing);
          session.merge(entity);
        }
        catch (final Throwable e) {
          handle(entity.getClass(), entity.getKey(), e);
        }

        inserted.add(entity);
      }
    }

    return inserted;
  }

  /**
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.persistence.PersistenceSession#doUpdateEntity(org.infodavid.common.persistence.IPersistentData)
   */
  @Override
  protected <T extends IPersistentData> T doUpdateEntity(final T entity)
      throws PersistenceException {
    T result = null;

    try {
      ((SessionImpl)manager.getDelegate()).merge(entity);
      result = entity;
    }
    catch (final NonUniqueObjectException e) {
      final SessionImpl session = (SessionImpl)manager.getDelegate();

      final T existing = (T)session.get(session.bestGuessEntityName(entity), entity.getKey());
      session.evict(existing);

      ((SessionImpl)manager.getDelegate()).merge(entity);
      result = entity;
    }
    catch (final Throwable e) {
      handle(entity.getClass(), entity.getKey(), e);

      result = null;
    }

    if (isDebugEnabled) {
      LOG.debug("Query result: " + result);
    }

    return result;
  }

  /**
   * Builds the transaction adapter.
   * @throws Exception the exception
   */
  protected synchronized void buildTransactionAdapter() throws Exception {
    if (transaction == null) {
      if (isDebugEnabled) {
        LOG.debug("Building transaction adapter");
      }

      transaction = new TransactionAdapter(this);
    }
  }

  /**
   * Gets the entity manager.
   * @return the entity manager
   */
  protected EntityManager getEntityManager() {
    return manager;
  }

  /*
   * (non-Javadoc)
   * @see org.infodavid.common.persistence.PersistenceSession#getCriteriaBuilder()
   */
  @Override
  public CriteriaBuilder getCriteriaBuilder() throws PersistenceException {
    return manager.getCriteriaBuilder();
  }

  /**
   * Handle.
   * @param obj the entity instance
   * @param exception the exception
   * @throws PersistenceException the persistence exception
   */
  protected void handle(final IPersistentData obj, final Throwable exception)
      throws PersistenceException {
    if (obj == null) {
      getProvider().getErrorHandler().handle(null, null, exception);
    }
    else {
      getProvider().getErrorHandler().handle(obj.getClass(), obj.getKey(), exception);
    }
  }

  /**
   * Handle.
   * @param type the type
   * @param id the id
   * @param exception the exception
   * @throws PersistenceException the persistence exception
   */
  protected void handle(final Class type, final Object id, final Throwable exception)
      throws PersistenceException {
    getProvider().getErrorHandler().handle(type, id, exception);
  }

  /*
   * (non-Javadoc)
   * @see org.infodavid.common.persistence.PersistenceSession#initialize()
   */
  @Override
  protected void initialize() throws PersistenceException {}

  /**
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.persistence.PersistenceSession#doFindEntitiesWithNamedQuery(java.lang.Class,
   *      java.lang.String, java.util.List)
   */
  @Override
  protected <T extends IPersistentData> List<T> doFindEntitiesWithNamedQuery(Class<T> clazz,
      String queryName, List<IQueryParameter> params) throws PersistenceException {
    List<T> result = null;

    try {
      final Query query = manager.createNamedQuery(queryName);

      HibernateHelper.getInstance().applyParameters(params, query);

      if (isDebugEnabled) {
        LOG.debug("Query: " + query.toString());
      }

      result = query.getResultList();
    }
    catch (final NoResultException e) {}
    catch (final Throwable e) {
      handle(clazz, null, e);
    }

    if (isDebugEnabled) {
      LOG.debug("Query result: " + result);
    }

    return result;
  }

  @Override
  protected <T extends IPersistentData> T doFindEntityWithNamedQuery(Class<T> clazz,
      String queryName, Serializable key, ELockType lockType) throws PersistenceException {
    T result = null;

    try {
      final Query query = manager.createNamedQuery(queryName);


      if (isDebugEnabled) {
        LOG.debug("Query: " + query.toString());
      }

      if (lockType == null) {
        result = (T)query.getSingleResult();
      }
      else {
        query.setLockMode(toLockModeType(lockType, hints));

        result = (T)query.getSingleResult();
      }

    }
    catch (final NoResultException e) {}
    catch (final Throwable e) {
      handle(clazz, null, e);
    }

    if (isDebugEnabled) {
      LOG.debug("Query result: " + result);
    }

    return result;
  }

}
